namespace ClassyBlog.Infrastructure.Specs.Unit
{
    using System;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq.Expressions;

    using Machine.Specifications;
    using NSubstitute;

    [Subject(typeof(PropertyAccessor))]
    public class when_creating_properties_accessor_with_null_target
    {
        static Exception exception;

        Because of = () => exception =
            Catch.Exception(() => new PropertyAccessor(null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_getting_property_with_null_binder
    {
        static PropertyAccessor accessor;
        static Exception exception;

        Establish context = () =>
            accessor = new PropertyAccessor(new object());

        Because of = () => exception =
            Catch.Exception(() =>
                                {
                                    object result;
                                    GetMemberBinder binder = null;

                                    accessor.TryGetMember(binder, out result);
                                });

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_getting_unknown_property_with_property_accessor
    {
        static PropertyAccessor accessor;
        static GetMemberBinder binder;
        static object result;

        Establish context = () =>
        {
            binder = Substitute.For<GetMemberBinder>(
                new object[] { "foo", false });
            accessor = new PropertyAccessor(new object());
        };

        Because of = () => accessor.TryGetMember(binder, out result);

        It should_return_null = () => result.ShouldBeNull();
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_getting_regular_property_with_property_accessor
    {
        static DummyObject dummy;
        static GetMemberBinder binder;
        static PropertyAccessor accessor;
        static bool found;
        static object result;

        Establish context = () =>
        {
            binder = Substitute.For<GetMemberBinder>(
                new object[] { "Foo", false });

            dummy = new DummyObject { Foo = "foo" };
            accessor = new PropertyAccessor(dummy);
        };

        Because of = () => found = accessor.TryGetMember(binder, out result);

        It should_match_property = () => found.ShouldBeTrue();

        It should_return_property_value = () => result.ShouldEqual("foo");

        private class DummyObject
        {
            public string Foo { get; set; }
        }
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_getting_dynamic_property_with_property_accessor
    {
        static IHasDynamicProperties dummy;
        static GetMemberBinder binder;
        static PropertyAccessor accessor;
        static bool found;
        static object result;

        Establish context = () =>
        {
            binder = Substitute.For<GetMemberBinder>(
                new object[] { "Foo", false });

            dummy = Substitute.For<IHasDynamicProperties>();
            dummy.DynamicPropertyNames.Returns(new[] { "Foo" });
            dummy.GetDynamicProperty("Foo").Returns("foo");

            accessor = new PropertyAccessor(dummy);
        };

        Because of = () => found = accessor.TryGetMember(binder, out result);

        It should_match_property = () => found.ShouldBeTrue();

        It should_return_property_value = () => result.ShouldEqual("foo");
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_setting_property_with_null_binder
    {
        static PropertyAccessor accessor;
        static Exception exception;

        Establish context = () =>
            accessor = new PropertyAccessor(new object());

        Because of = () => exception =
            Catch.Exception(() =>
            {
                SetMemberBinder binder = null;

                accessor.TrySetMember(binder, new object());
            });

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_setting_unknown_property_with_property_accessor
    {
        static PropertyAccessor accessor;
        static SetMemberBinder binder;
        static bool found;

        Establish context = () =>
        {
            binder = Substitute.For<SetMemberBinder>(
                new object[] { "foo", false });
            accessor = new PropertyAccessor(new object());
        };

        Because of = () => found = accessor.TrySetMember(binder, "foo");

        It should_set_property_value = () => found.ShouldBeTrue();
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_setting_regular_property_with_property_accessor
    {
        static DummyObject dummy;
        static SetMemberBinder binder;
        static PropertyAccessor accessor;
        static bool found;

        Establish context = () =>
        {
            binder = Substitute.For<SetMemberBinder>(
                new object[] { "Foo", false });

            dummy = new DummyObject();
            accessor = new PropertyAccessor(dummy);
        };

        Because of = () => found = accessor.TrySetMember(binder, "foo");

        It should_match_property = () => found.ShouldBeTrue();

        It should_set_property_value = () => dummy.Foo.ShouldEqual("foo");

        private class DummyObject
        {
            public string Foo { get; set; }
        }
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_setting_readonly_property_with_property_accessor
    {
        static DummyObject dummy;
        static SetMemberBinder binder;
        static PropertyAccessor accessor;
        static bool found;

        Establish context = () =>
        {
            binder = Substitute.For<SetMemberBinder>(
                new object[] { "Foo", false });

            dummy = new DummyObject();
            accessor = new PropertyAccessor(dummy);
        };

        Because of = () => found = accessor.TrySetMember(binder, "foo");

        It should_not_match_property = () => found.ShouldBeFalse();

        private class DummyObject
        {
            public string Foo { get; private set; }
        }
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_setting_dynamic_property_with_property_accessor
    {
        static IHasDynamicProperties dummy;
        static SetMemberBinder binder;
        static PropertyAccessor accessor;
        static bool found;

        Establish context = () =>
        {
            binder = Substitute.For<SetMemberBinder>(
                new object[] { "Foo", false });

            dummy = Substitute.For<IHasDynamicProperties>();

            accessor = new PropertyAccessor(dummy);
        };

        Because of = () => found = accessor.TrySetMember(binder, "foo");

        It should_match_property = () => found.ShouldBeTrue();

        It should_set_property_value = () =>
            dummy.Received().SetDynamicProperty("Foo", "foo");
    }

    [Subject(typeof(PropertyAccessor))]
    public class when_getting_dynamic_member_names_of_property_accessor
    {
        static PropertyAccessor accessor;
        static IEnumerable<string> members;

        Establish context = () =>
        {
            var dummy = new DummyObject();
            dummy.SetDynamicProperty("Bar", new object());
            accessor = new PropertyAccessor(dummy);
        };

        Because of = () => members = accessor.GetDynamicMemberNames();

        It should_include_regular_properties = () =>
            members.ShouldContain("Foo");

        It should_include_dynamic_properties = () =>
            members.ShouldContain("Bar");

        private sealed class Dummy : IHasDynamicProperties
        {
            private readonly IDictionary<string, object> dynamicProperties =
                new Dictionary<string, object>();

            public string Foo { get; set; }

            public IEnumerable<string> DynamicPropertyNames
            {
                get { return dynamicProperties.Keys; }
            }

            public DynamicMetaObject GetMetaObject(Expression parameter)
            {
                return new DynamicPropertiesMetaObject(parameter, this);
            }

            public object GetDynamicProperty(string name)
            {
                object value;

                return dynamicProperties.TryGetValue(name, out value) ?
                       value :
                       null;
            }

            public void SetDynamicProperty(string name, object value)
            {
                dynamicProperties[name] = value;
            }
        }
    }
}